package cn.edu.fzu.homemaking.redis;

import com.alibaba.fastjson.JSON;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.pool.KryoFactory;
import com.esotericsoftware.kryo.pool.KryoPool;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * Redis拦截器
 * 
 * @author fangxin
 * @date 2023/04/10
 */
@Aspect
@Component
public class RedisInterceptor {

    private static final int      SIZE      = 64;

    private static final int      MAX_SIZE  = 64 * 1024;

    private static final KryoPool KRYO_POOL = new KryoPool.Builder(
            // factory
            new KryoFactory() {
                public Kryo create() {
                    return new Kryo();
                }
            }).build();

    @Resource
    private RedisClient           redisClient;

    @Around("@annotation(redis)")
    public Object doAround(ProceedingJoinPoint pjp, Redis redis) throws Throwable {

//        MethodSignature signature = (MethodSignature) pjp.getSignature();
//        Method method = signature.getMethod();

        // 是否穿透 redis
        boolean stab = redis.action() == RedisAction.STAB_REDIS;

        Object[] keyArgs = getKeyArgs(pjp.getArgs(), redis.keyArgs());
        String key = keyArgs == null ? redis.value() : String.format(redis.value(), keyArgs);

//        Class<?> returnType = method.getReturnType();
        Object result = stab ? null : get(key);
        if (result == null) {
            result = pjp.proceed();
            if (result != null) {
                setex(key, redis.ttl(), result);
            } else if (redis.cacheNull()) {
                setex(key, redis.nullTtl(), null);
            }
        }
        return result;
    }


    /** 获取构造 redis 的 key 的参数数组 */
    private Object[] getKeyArgs(Object[] args, int[] keyArgs) {

        Object[] redisKeyArgs;
        int len = keyArgs.length;
        if (len == 0) {
            return null;
        } else {
            len = min(len, args.length);
            redisKeyArgs = new Object[len];
            int i = 0;
            for (int n : keyArgs) {
                redisKeyArgs[i++] = args[n];
                if (i >= len) {
                    break;
                }
            }
            return redisKeyArgs;
        }
    }


    private int min(int i, int j) {
        return Math.min(i, j);
    }


    private <T> void setex(final String key, final int ttl, final T data) {
        Kryo kryo = KRYO_POOL.borrow();
        try (Output output = new Output(SIZE, MAX_SIZE)) {
            kryo.writeClassAndObject(output, data);
            redisClient.opsForValue().set(key, JSON.toJSONString(output.getBuffer()), ttl, TimeUnit.SECONDS);
        } finally {
            KRYO_POOL.release(kryo);
        }
    }


    @SuppressWarnings("unchecked")
    private <T> T get(String key) {
        byte[] value = (byte[]) redisClient.opsForValue().get(key);
        if (value == null) {
            return null;
        }
        Kryo kryo = KRYO_POOL.borrow();
        try (Input input = new Input(value)) {
            Object o = kryo.readClassAndObject(input);
            return (T) o;
        } finally {
            KRYO_POOL.release(kryo);
        }
    }
}
